package cn.com.saintlee.demo.thread;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * 使用Semaphore、Callable和FutureTask组合实现控制多线程执行，并获取线程运行情况。
 * <p>
 * 1. 通过信号量{@link java.util.concurrent.Semaphore}控制每次运行的线程数。
 * 2. 创建类实现{@link java.util.concurrent.Callable}生成线程运行结果。
 * 3. 使用异步任务{@link java.util.concurrent.FutureTask}获取线程运行结果，
 * 并{@link java.util.concurrent.FutureTask#get(long, TimeUnit)}设置超时时间。
 *
 * @author SaintLee
 */
public class SemaphoreCallableTest {

    public static void main(String[] args) {
        SemaphoreCallableTest threadTest = new SemaphoreCallableTest();
        threadTest.runThread();
    }

    private void runThread() {
        // 创建线程池。初始化十个线程，和JDBC连接池是一个意思，实现重用
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 进行异步任务集合
        List<FutureTask<CallableBean>> futureTasks = new ArrayList<>();

        // 信号量，最多10个线程同时执行
        Semaphore semaphore = new Semaphore(10, true);

        for (int i = 1; i <= 100; i++) {
            // 传入Callable对象创建FutureTask对象
            SemaphoreCallable callable = new SemaphoreCallable(semaphore, "SemaphoreThread-" + i);
            FutureTask<CallableBean> futureTask = new FutureTask<>(callable);
            futureTasks.add(futureTask);
            // 提交异步任务到线程池，由于是异步并行任务，所以这里不会阻塞
            service.submit(futureTask);
        }

        for (FutureTask<CallableBean> task : futureTasks) {
            try {
                // 获取结果，这里开始阻塞
                CallableBean bean = task.get(10, TimeUnit.SECONDS);
                System.out.println(bean.isResult());
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                e.printStackTrace();
            }
        }

        System.out.println("主线程");
        // 关闭线程池
        service.shutdown();

    }

    /**
     * 实现Callable接口，定义可返回结果的线程任务
     *
     * @author SaintLee
     */
    class SemaphoreCallable implements Callable<CallableBean> {

        private final Semaphore semaphore;

        private String info;

        SemaphoreCallable(Semaphore semaphore, String info) {
            this.semaphore = semaphore;
            this.info = info;
        }

        @Override
        public CallableBean call() throws Exception {
            CallableBean bean = new CallableBean();
            try {
                // 获取许可
                semaphore.acquire();

                // 模拟线程耗时和任务执行
                int result = (new java.util.Random()).nextInt(10);
                Thread.sleep(1000 * result);
                System.out.println(info);

                // 释放许可
                semaphore.release();
                bean.setResult(true);
            } catch (Exception e) {
                e.printStackTrace();
                bean.setResult(false);
                bean.setException(e);
            }
            return bean;
        }
    }

    /**
     * Callable信息
     *
     * @author SaintLee
     */
    class CallableBean implements Serializable {

        private static final long serialVersionUID = 6938152784698193723L;

        /**
         * 结果
         */
        private boolean result;

        /**
         * 异常
         */
        private Exception exception;

        public CallableBean() {
        }

        public CallableBean(boolean result, Exception exception) {
            this.result = result;
            this.exception = exception;
        }

        public boolean isResult() {
            return result;
        }

        public void setResult(boolean result) {
            this.result = result;
        }

        public Exception getException() {
            return exception;
        }

        public void setException(Exception exception) {
            this.exception = exception;
        }
    }
}
